#include <system.h>
#include <common.h>
#include <scrn.h>
#include <isr.h>
#include <page.h>
#include <kheap.h>

#define INDEX_BITS(a) ((a)/32)
#define OFFSET_BITS(a) ((a)%32)

u32int * frames;
u32int nframes;

extern u32int placement_addr ;


page_directory_t* kerneldir=0;
page_directory_t* currentdir=0;

static void set_frame(u32int frame_addr)
{
   u32int frm = frame_addr/0x1000;
   u32int idx = INDEX_BITS(frm);
   u32int off = OFFSET_BITS(frm);

   frames[idx] |= 0x1<<off;
}

static void clear_frame(u32int frame_addr)
{
   u32int frm = frame_addr/0x1000;
   u32int idx = INDEX_BITS(frm);
   u32int off = OFFSET_BITS(frm);

   frames[idx] &= ~(0x1<<off);  
}

static u32int test_frame(u32int frame_addr)
{
   u32int frm = frame_addr/0x1000;
   u32int idx = INDEX_BITS(frm);
   u32int off = OFFSET_BITS(frm);

   return frames[idx]&(0x1 << off);
}

static u32int first_frame()
{
   u32int i,j;
   for(i=0;i<INDEX_BITS(nframes);i++)
   {
      if(frames[i]!=0xFFFFFFFF)
      {
        for(j=0;j<32;j++)
        {
           if(!(frames[i]&(0x1 << j)))
              return i*32+j; 
        }
      }
   }
   return -1;
}


page_t *get_page(u32int address, int make,page_directory_t *dir)
{
   address = address/0x1000;
   u32int idx = address/1024;
   if (dir->tables[idx])
   {
      return &dir->tables[idx]->pages[address%1024];
   }else if (make)
   {
       u32int tmp;
       dir->tables[idx]=(page_table_t*)kmalloc_ap(sizeof(page_table_t),&tmp);
       //tmp &= 0xFFFFF000;
       tmp |= 0x7;
       dir->phy_tables[idx] = tmp;
       return &dir->tables[idx]->pages[address%1024]; 
   }else
   {
     return 0;
   };
}


void alloc_kernel_frame(page_t* page)
{
    //page_t* page = get_page(addr,1,kerneldir);
   
    ASSERT(page);

    if(page->frame !=0)
       return;
    s32int idx = first_frame();
     
    ASSERT(idx>=0);

    /*no check for page*/
    set_frame(idx*0x1000);
    page->present = 1;
    page->rw = 0;
    page->user = 0;
    page->frame = idx;
}

void initialize_paging()
{

   nframes = MEM_MAX /0x1000;
   frames = (u32int*)kmalloc(INDEX_BITS(nframes)*sizeof(u32int));
   memset((u8int*)frames,0x0,sizeof(u32int)*INDEX_BITS(nframes));
   
   kerneldir = (page_directory_t*)kmalloc_a(sizeof(page_directory_t));
   memset((u8int*)kerneldir,0x0,sizeof(page_directory_t));
   currentdir = kerneldir;

   u32int i = 0;
   for(i=0;i<KHEAP_MAX_SIZE;i+=0x1000)
   {
      get_page(KHEAP_START+i,1,kerneldir);
   }

   i=0;
   while(i<placement_addr+0x1000)
   {
      // alloc_frame(get_page(i,1,kerneldir),0,0);
       alloc_kernel_frame(get_page(i,1,kerneldir));
       i+=0x1000;
   }
      
   register_interrupt_handler(14, page_fault);

   switch_page_directory(kerneldir);

   init_heap(KHEAP_START,KHEAP_START+KHEAP_INITIAL_SIZE,KHEAP_MAX_SIZE);
}

void switch_page_directory(page_directory_t *dir)
{
    currentdir = dir;
  
    asm volatile("mov %0, %%cr3"::"r"(&dir->phy_tables));
    u32int cr0;
    asm volatile("mov %%cr0, %0":"=r"(cr0));
    cr0 |= 0x80000000;//enable paging
    asm volatile("mov %0,%%cr0"::"r"(cr0));
}

void page_fault(registers_t regs)
{
   u32int faulting_addr;
   asm volatile("mov %%cr2, %0":"=r" (faulting_addr));

   int present = !(regs.err_code & 0x1);
   int rw = regs.err_code & 0x2;
   int us = regs.err_code & 0x4;
   int reserved = regs.err_code & 0x8;
   //int id = regs.err_code & 0x10;

   puts("Page fault (");
   if(present){puts("present");};
   if(rw){puts("read-only");};
   if(us){puts("user-mode");};
   if(reserved){puts("reserved");};
   puts(") at");
   puts_hex(faulting_addr);
   puts("\n");
   PANIC("Page fault"); 
}


